package com.dirox.confluence.labelmanagement.service.imp;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.atlassian.confluence.core.ConfluenceEntityObject;
import com.atlassian.confluence.core.service.ValidationError;
import com.atlassian.confluence.labels.Label;
import com.atlassian.confluence.labels.LabelManager;
import com.atlassian.confluence.labels.Labelable;
import com.atlassian.confluence.labels.service.AddLabelsCommand;
import com.atlassian.confluence.labels.service.LabelsService;
import com.atlassian.confluence.labels.service.RemoveLabelCommand;
import com.atlassian.confluence.labels.service.ValidateLabelsCommand;
import com.atlassian.confluence.pages.Attachment;
import com.atlassian.confluence.pages.BlogPost;
import com.atlassian.confluence.pages.Page;
import com.atlassian.confluence.pages.PageManager;
import com.atlassian.confluence.security.Permission;
import com.atlassian.confluence.security.PermissionManager;
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
import com.atlassian.user.User;
import com.dirox.confluence.labelmanagement.service.LabelManagementService;
import static com.google.common.base.Preconditions.checkNotNull;

public class LabelManagementServiceImp implements LabelManagementService {

	private SpaceManager spaceManager;
	private LabelManager labelManager;
	private LabelsService labelsService;
	private PermissionManager permissionManager;
	private PageManager pageManager;

	/**
	 * <p>getLabelCountInSpace method:<br/>
	 * Used to count number of labels
	 * which current users have EDIT permission on.<br/>
	 * Affected on a specific space.</p>
	 *
	 * @param spaceKey
	 * 
	 * @return map of Label objects and number of that labels 
	 * 
	 * @see LabelManagementService#getLabelsCountInSpace(String spaceKey)
	 */
	@Override
	public Map<Label, Integer> getLabelsCountInSpace(String spaceKey) {
		List<Space> spaceList = new ArrayList<Space>();
		Space space = spaceManager.getSpace(spaceKey);
		if (space != null) {
			spaceList.add(space);
		}
		return getLabelsCount(spaceList);
	}

	/**
	 * <p>getLabelCountInAllSpaces method:<br/>
	 * Used to count number of labels
	 * which the current user have EDIT permission on.<br/>
	 * Affect all spaces.</p>
	 *
	 * @return map of Label objects and number of that labels 
	 *
	 * @see LabelManagementService#getLabelsCountInAllSpaces()
	 */
	@Override
	public Map<Label, Integer> getLabelsCountInAllSpaces() {
		return getLabelsCount(getSpaces());
	}

	/**
	 * <p>renameLabeInAllSpace method:<br/> 
	 * Used to rename labels from all spaces.
	 * Take old-label, new-label then rename old-label to new-label.<br/>
	 * Affect all spaces.</p>
	 * 
	 * @param fromLabelName
	 * @param toLabelName
	 * 
	 * @return number of affected entity contents.
	 * 
	 * @see LabelManagementService#renameLabelInAllSpaces(String fromLabelName,
	 *      String toLabelName)
	 */
	@Override
	public long renameLabelInAllSpaces(String fromLabelName, String toLabelName) {
		return mergeLabelInAllSpaces(fromLabelName, toLabelName);
	}

	/**
	 * <p>renameLabeInSpace method: used to rename labels in space.
	 * Take spaceKey, old-label, new-label then rename old-label to new-label.
	 * Affect only on that space.</p>
	 * 
	 * @param SpaceKey
	 * @param fromLabelName
	 * @param toLabelName
	 * 
	 * @return number of affected entity contents.
	 * 
	 * @see LabelManagementService#renameLabelInSpace(String spaceKey,String
	 *      fromLabelName, String toLabelName)
	 */
	@Override
	public long renameLabelInSpace(String spaceKey, String fromLabelName,
									String toLabelName) {
		return mergeLabelInSpace(spaceKey, fromLabelName, toLabelName);
	}

	/**
	 * <p>mergeLabelInSpace method: used to merge label in all spaces.<br/>
	 * Take first and second label, then find those in all spaces
	 * and merge all labels with those name.</p>
	 * 
	 * @param fromLabelName
	 * @param toLabelName
	 * 
	 * @return number of affected contents.
	 * @see LabelManagementService#renameLabelInAllSpaces(String fromLabelName,
	 *      String toLabelName)
	 */
	@Override
	public long mergeLabelInAllSpaces(String fromLabelName, String toLabelName) {
		return mergeLabels(getSpaces(), fromLabelName, toLabelName);
	}

	/**
	 * <p>mergeLabelInSpace method: used to merge label in space.<br/>
	 * Take spaceKey, then find and merge all labels in that space.</p>
	 * 
	 * @param spaceKey
	 * @param fromLabelName
	 * @param toLabelName
	 * 
	 * @return affected entity contents.
	 * 
	 * @see LabelManagementService#mergeLabelInSpace(String spaceKey,String
	 *      fromLabelName, String toLabelName)
	 */
	@Override
	public long mergeLabelInSpace(String spaceKey, String fromLabelName,
									String toLabelName) {
		List<Space> spaceList = new ArrayList<Space>();
		Space space = spaceManager.getSpace(spaceKey);
		if (space != null) {
			spaceList.add(space);
		}
		return mergeLabels(spaceList, fromLabelName, toLabelName);

	}

	protected long mergeLabels(List<Space> spaceList, String fromLabelName,
			   					 String toLabelName) {
		long result = 0;
		User user = getCurrentUser();
		Label fromLabelObj=labelManager.getLabel(fromLabelName);
		if (fromLabelObj==null) return result; 
		for (Space space : spaceList) {
			for (Page page : getPagesInSpace(space)) {
				if (page.getLabels().contains(fromLabelObj)
						&& mergeLabel(user, page, fromLabelObj.getId(), 
									  toLabelName)) {
					result++;
				}
				for (Attachment attachment : page.getAttachments()) {
					if (attachment.getLabels().contains(fromLabelObj)
							&& mergeLabel(user, attachment, fromLabelObj.getId(), 
										  toLabelName)) {
						result++;
					}
				}
			}

			for (BlogPost blogPost : getBlogPostsInSpace(space)) {
				if (blogPost.getLabels().contains(fromLabelObj)
						&& mergeLabel(user, blogPost, fromLabelObj.getId(), 
									  toLabelName)) {
					result++;
				}

				for (Attachment attachment : blogPost.getAttachments()) {
					if ((fromLabelObj!=null) 
							&& attachment.getLabels().contains(fromLabelObj)
							&& mergeLabel(user, attachment, fromLabelObj.getId(), toLabelName)) {
							result++;
					}
				}
			}
		}
		return result;
	}

	protected Map<Label, Integer> getLabelsCount(List<Space> spaceList) {
		User user = getCurrentUser();
		Map<Label, Integer> results = new HashMap<Label, Integer>();
		Integer currentValue;
		for (Space space : spaceList) {
			// page
			for (Page page : getPagesInSpace(space)) {
				for (Label label : page.getLabels()) {
					if (hasEditPermission(label, user, page)) {
						currentValue = results.get(label);
						currentValue = currentValue == null ? 0 : currentValue;
						results.put(label, (currentValue + 1));
					}
				}
				// count with page attachment
				for (Attachment attachment : page.getAttachments()) {
					for (Label label : attachment.getLabels()) {
						if (hasEditPermission(label, user, attachment)) {
							currentValue = results.get(label);
							currentValue = currentValue == null ? 0
									: currentValue;
							results.put(label, (currentValue + 1));
						}
					}
				}
			}
			// blogPost
			for (BlogPost blogPost : getBlogPostsInSpace(space)) {
				for (Label label : blogPost.getLabels()) {
					if (hasEditPermission(label, user, blogPost)) {
						currentValue = results.get(label);
						currentValue = currentValue == null ? 0 : currentValue;
						results.put(label, (currentValue + 1));
					}
				}
				// count with blogPost attachment
				for (Attachment attachment : blogPost.getAttachments()) {
					for (Label label : attachment.getLabels()) {
						if (hasEditPermission(label, user, attachment)) {
							currentValue = results.get(label);
							currentValue = currentValue == null ? 0
									: currentValue;
							results.put(label, (currentValue + 1));
						}
					}
				}
			}
		}
		return results;
	}

	protected boolean mergeLabel(User user, Labelable labelable,
			long labelId, String toLabelName) {
		RemoveLabelCommand removeCommand = null;
		AddLabelsCommand addCommand = null;
		Label toLabelObj=labelManager.getLabel(toLabelName);
		
		if ((toLabelObj==null) || !labelable.getLabels().contains(toLabelObj)) {
			addCommand = labelsService.newAddLabelCommand(toLabelName, user,
					((ConfluenceEntityObject) labelable).getId(),
					getEntityType(labelable));
		}

		removeCommand = labelsService.newRemoveLabelCommand(labelId, user,
				((ConfluenceEntityObject) labelable).getId());

		if (addCommand == null) {
			if (removeCommand.isValid()) {
				removeCommand.execute();
			} else {
				return false;
			}
		} else if (addCommand.isValid() && removeCommand.isValid()) {
			addCommand.execute();
			removeCommand.execute();
		} else
			return false;

		return true;
	}
	
	/**
	 * <p>deleteLabelInAllSpace() method:<br/>
	 * Used to deleting all labels with name input in all spaces.</p>
	 * 
	 * @param labelName
	 * @return number of affected contents
	 * 
	 * @see LabelManagementService#deleteLabelInAllSpaces(String)
	 */
	@Override
	public long deleteLabelInAllSpaces(String labelName) {
		return deleteLabels(getSpaces(), labelName);
	}
	
	/**
	 * <p>deleteLabelInSpace() method:<br/>
	 * Used to deleting all labels with name input in specific space.</p>
	 * 
	 * @param spaceKey
	 * @param labelName
	 * 
	 * @return number of affected contents
	 * 
	 * @see LabelManagementService#deleteLabelInSpace(String,String)
	 */
	@Override
	public long deleteLabelInSpace(String spaceKey, String labelName) {
		List<Space> spaceList = new ArrayList<Space>();
		Space space = spaceManager.getSpace(spaceKey);
		if (space != null) {
			spaceList.add(space);
		}
		return deleteLabels(spaceList, labelName);
	}

	protected long deleteLabels(List<Space> spaceList, String labelName) {
		long result = 0;
		User user = getCurrentUser();
		Label labelObj=labelManager.getLabel(labelName);
		if (labelObj==null) return result;
		for (Space space : spaceList) {
//			remove label in pages of space
			for (Page page : getPagesInSpace(space)) {
				if (page.getLabels().contains(labelObj) 
						&& removeLabel(labelObj.getId(), user, page)) {
					result++;
				}
//				remove label in attachments of page
				for (Attachment attachment : page.getAttachments()) {
					if (attachment.getLabels().contains(labelObj)
							&& removeLabel(labelObj.getId(), user, attachment)) {
						result++;
					}
				}
			}
//			remove label in blogPosts of space
			for (BlogPost blogPost : getBlogPostsInSpace(space)) {
				if (blogPost.getLabels().contains(labelObj)
						&& removeLabel(labelObj.getId(), user, blogPost)) {
					result++;
				}
//				remove label in attachments of BlogPost
				for (Attachment attachment : blogPost.getAttachments()) {
					if (attachment.getLabels().contains(labelObj)
							&& removeLabel(labelObj.getId(), user, attachment)) {
						result++;
					}
				}
			}
		}
		return result;
	}

	/**
	 * Remove label for Labelable with user inputed
	 * <p>
	 * 
	 * @param labelName
	 *            - String name of label
	 * @param User
	 *            object - Current user
	 * @param Labelable
	 *            object - Target object to remove label
	 * @return <code>true</code> if already removed, <code>false</code> if not
	 *         able to remove
	 * 
	 */
	protected boolean removeLabel(long labelId, User user, Labelable labelable) {
		RemoveLabelCommand removeCommand = labelsService.newRemoveLabelCommand(
				labelId, user, ((ConfluenceEntityObject) labelable).getId());
		if (removeCommand.isValid()) {
			removeCommand.execute();
			return true;
		} else
			return false;
	}

	/**
	 * Check user edit permission for object
	 * 
	 * @param Label
	 *            object - default set null, not implemented yet.
	 * @param User
	 *            object- Current user
	 * @param Object
	 *            . Return null on error
	 * @return <code>true</code> if has permission. <code>false</code> if
	 *         doesn't have permission.
	 */
	protected boolean hasEditPermission(Label label, User user, Object object) {
		return permissionManager.hasPermission(user, Permission.EDIT, object);
	}

	/**
	 * Get current login user
	 * 
	 * @return User object - current user
	 */
	protected User getCurrentUser() {
		return AuthenticatedUserThreadLocal.getUser();
	}

	/**
	 * Get All Spaces
	 * 
	 * @return List of Spaces
	 */
	protected List<Space> getSpaces() {
		return spaceManager.getAllSpaces();
	}
	/**
	 * Get All Spaces
	 * 
	 * @return List of Spaces
	 */
	protected List<Space> getSpacesWithLabel(String label) {
		return spaceManager.getAllSpaces();
	}

	/**
	 * Get All current BlogPosts in Space
	 * 
	 * @param Space
	 *            object - target space
	 * @return List of BlogPosts
	 */
	protected List<BlogPost> getBlogPostsInSpace(Space space) {
		return pageManager.getBlogPosts(space, true);
	}

	/**
	 * Get All current Pages in Space
	 * 
	 * @param Space
	 *            object - target space
	 * @return List of Pages
	 */
	protected List<Page> getPagesInSpace(Space space) {
		return pageManager.getPages(space, true);
	}
	/**
	 * Get Entity Type of Object
	 * 
	 * @param Object
	 *            
	 * @return String value - type of Object
	 * 			Null - object is not instanceOf (Space, Page, Attachment, BlogPost)
	 */

	protected String getEntityType(Object object) {
		if (object instanceof Space) {
			return "Space";
		} else if (object instanceof Page) {
			return "Page";
		} else if (object instanceof Attachment) {
			return "Attachment";
		} else if (object instanceof BlogPost) {
			return "BlogPost";
		} else
			return null;
	}
	/**
	 * @see LabelManagementService#getLabelValidationError(String)
	 */

	public ValidationError getLabelValidationError(String labelName) {
		ValidateLabelsCommand command = labelsService.newValidateLabelCommand(
				labelName, getCurrentUser());
		if (!command.isValid()) {
			return command.getValidationErrors().iterator().next();
		} else {
			return null;
		}
	}

	public void setSpaceManager(SpaceManager spaceManager) {
		this.spaceManager = checkNotNull(spaceManager);
	}

	public void setLabelManager(LabelManager labelManager) {
		this.labelManager = checkNotNull(labelManager);
	}

	public void setLabelsService(LabelsService labelsService) {
		this.labelsService = checkNotNull(labelsService);
	}

	public void setPermissionManager(PermissionManager permissionManager) {
		this.permissionManager = checkNotNull(permissionManager);
	}

	public void setPageManager(PageManager pageManager) {
		this.pageManager = checkNotNull(pageManager);
	}

	@Override
	public boolean isExistLabel(String labelName) {
		return labelManager.getLabel(labelName) != null;
	}

	@Override
	public boolean isExistLabelInSpace(String spaceKey, String labelName) {
		Label label = labelManager.getLabel(labelName);
		return (label != null && labelManager.getLabelsInSpace(spaceKey)
				.contains(label));
	}
}
